/******************************************************************************
 * $Id: parser.c,v 1.141 2004/09/22 01:14:23 jimb Exp $
 * @file
 * @author  Jim Brooks http://www.jimbrooks.org
 * @brief   Parse function definitions and function calls in C/C++ code.
 * @remarks @verbatim
 * - MSDOG lines end with 0d0a (hex)
 *   UNIX  lines end with   0a (hex)
 * - Sometimes, a function definition that doesn't appear in the tree view
 *   may actually have been parsed but Exuberant ctags didn't tag it and
 *   consequently AddFuncDefToGraph() will ignore it.  Exuberant ctags
 *   won't tag functions inside #if 0, or if braces were mismatched, etc.
 * - The code is based on ASCIIZ strings so
 *   \0 is temporarily written to end of buffer as well as
 *   segments within the buffer.
 * - Had difficulties using complex regex patterns.
 *   Therefore, the code was factored into several small subroutines
 *   that search only for a simple pattern.  These search subroutines
 *   also abstract whether regex code is used or not.
 * - Once upon a time the parser was implemented in flex/bison
 *   but it had so many kludges that it devolved into a hydra:
 *   fixing one bug created more!
 * - Parsing ANSI C vs. K&R can be conflicting.
 *   The problem is that K&R style allows semicolons in the head of
 *   a function definition which the parser could very easily mistake
 *   as a function prototype.  This is why I decided that K&R functions
 *   shouldn't be parsed by default (the -knr arg must be passed).
 * - The order of how AddFuncDefToGraph()/AddFuncCallToGraph() work:
 *   1. A list of function calls is built by calling AddFuncCallToGraph().
 *   2. AddFuncDefToGraph() is called which, in addition to graphing
 *      the func def, graphs the prior list of func calls.
 * @endverbatim
 ******************************************************************************/

#define PARSER_C
#include "common.h"
#include "parser.h"
#include "funcgraph.h"
#include "debug.h"

/*
 * Static vars.
 */
static char*  pModuleName;

/*
 * Regex patterns for parts of function definitions, inline methods, and function calls.
 * Yes, newlines in the regex patterns are written unescaped as "\n", not "\\n".
 * Complex regex patterns will cause regexec() to execute far too slowly
 * (such patterns that used to be here were replaced by C code).
 */
/* ----- class declaration ----- */
static regex_t         re_classPart1;           /* class.*\n{ */
static const char pattern_classPart1[] =
   "\nclass[^\n;{}]*\n\\{";
/* ----- inline method part1 ----- */
static regex_t         re_inlinePart1;          /* method().*{ */
static const char pattern_inlinePart1[] =
   "~?[a-zA-Z_]+[a-zA-Z_0-9]*[ ]?\\("           /* method( */
   "[^\n;{}]*\\{";                              /* til opening {   */

/*----------------------------------------------------------------------------
 * public functions
 *---------------------------------------------------------------------------*/

/*****************************************************************************
 * One-time initialization.
 *****************************************************************************/
void
InitParser( void )
{
  /*
   * Precompile regex patterns for speed.
   */
   const int cflags = REG_EXTENDED | REG_NEWLINE; 
ASSERT( cmdLineArgsProcessed );
   regcomp( &re_classPart1,  pattern_classPart1,  cflags );
   regcomp( &re_inlinePart1, pattern_inlinePart1, cflags );
   return;
}

/*****************************************************************************
 * Parse function definitions and calls in a module (source file).
 * @remarks SIDE EFFECT: MODIFIES BUFFER!
 *****************************************************************************/
void
ParseFuncsInModule( char* ibuf, int ilen, char* pInputName )
{
   char       lastCharSaved         = 0;
   gboolean   needToRestoreLastChar = FALSE;

  /*
   * Skip tiny files (esp. size==0).
   */
ASSERT( ibuf != NULL );
   if ( UX(ilen < 10  ||  ilen > MAX_TEXT_BUFFER_LEN) ) return;

  /*
   * String functions which are called later depend
   * on the buffer being terminated by a null char.
   */
   if ( UX(ibuf[ilen] != '\0') )
   {
     /*
      * No null char is after the buffer.
      * Change the last char to a null char.
      */
      --ilen;
      lastCharSaved = ibuf[ilen];
      ibuf[ilen] = '\0';
      needToRestoreLastChar = TRUE;
   }

  /*
   * Invoke the parser.
   */
   pModuleName = pInputName; /* this won't dangle */
   ParseModule( ibuf, ilen );

  /*
   * Restore last char if necessary.
   */
   if ( UX(needToRestoreLastChar) )
      ibuf[ilen] = lastCharSaved;

   return;
}

/*----------------------------------------------------------------------------
 * private functions
 *---------------------------------------------------------------------------*/

/*****************************************************************************
 * Top-level of parser.
 *****************************************************************************/
static void
ParseModule( char* ibuf, int ilen )
{
  /*
   * Class declarations are often placed at the top of a file, so parsing them
   * first will usually keep the function tree view in the same order (not always).
   */
   Preprocess( ibuf, ilen );
   if ( browsingCPlusPlus || browsingJava )
      ParseEvery( ibuf, ilen, ParseClassInlines );
   ParseEvery( ibuf, ilen, ParseFuncDef );

   return;
}

/*****************************************************************************
 * Preprocess buffer to simplify parsing.
 * Eg, a parenthesis inside a string would trip the parser.
 * Comments are changed to spaces.  But strings are changed to Xs in case
 * it is passed as an argument (to avoid confusing parser by an empty arg).
 * @pre     BUFFER MUST BE TERMINATED BY A NULL CHAR (after "ilen").
 *          BUFFER LENGTH > 0
 * @remarks SIDE EFFECT: MODIFIES BUFFER!
 *****************************************************************************/
static void
Preprocess( char* ibuf, int ilen )
{
   char* end = ibuf+ilen;
   char* pc;
   for ( pc = ibuf; pc < end; ++pc )
   {
      if ( *pc < 34 || *pc > 47 ) continue;     /* this line reduces time by 1/2 (PentiumIII) */
/*    if ( (*pc & 0xf0) != 0x20 ) continue; */  /* slower as it includes space (0x20) */
      switch ( *pc )
      {
         /* --- C or C++ comment? -------------------- */
         case '/':
         {
            switch ( *(pc+1) )
            {
               case '*': /* C comment */
                 /*
                  * Until end of C comment or buffer.
                  */
                  do { PREPROC_CHANGE_NOT_EOL( ' ' ); ++pc; }
                  while ( *pc  &&  (*pc != '*' || *(pc+1) != '/') );
                  if ( EX(*pc) )
                  {
                     *(pc++) = ' '; /* change the two ending chars */
                     *pc     = ' ';
                  }
               break;

               case '/': /* C++ comment */
                 /*
                  * Until end of line or buffer.
                  */
                  while ( *pc  &&  *pc != '\n' )
                  { PREPROC_CHANGE( ' ' ); ++pc; }
               break;
            }
         }
         break;

         /* --- double quote? ------------------------ */
         case '"':
         {
           /*
            * There are some C source files out there with very long strings
            * so the length of strings isn't limited (would cause parse problems).
            */
            ++pc;
            if ( *pc == '"' ) break; /* in case of "" */
            do
            {
              /*
               * Don't treat line continuation followed by newline
               * as an escaped char (a rare problem case but an example
               * exists in linux/scripts/lxdialog/lxdialog.c).
               */
               if ( *pc != '\\' || *(pc+1) == '\n' )
               {
                  PREPROC_CHANGE_NOT_EOL( 'X' ); ++pc;
               }
               else
               {
                 /*
                  * This is an escaped char.  Change both bytes unless null char.
                  */
                  if ( EX( *(pc+1) ) )
                  { *pc = *(pc+1) = 'X'; pc += 2; }
                  else break;
               }
            }
            while ( *pc  &&  *pc != '"' );
#ifdef HYPERSRC_DEBUG
            if ( UX(*pc==0) )
            {
               Warning( "unterminated double-quoted string in module %s near line %d\n",
                        pModuleName?pModuleName:"N/A", DebugGetLineCount() );
            }
#endif
         }
         break;

         /* --- single quote? ------------------------ */
         case '\'':
         {  /*
             * Problem case is '\''
             * Longest case is '\123' (5 chars total)
             */
            char c;
            PREPROC_CHANGE( 'X' ); ++pc;           /* #1 first squote */
            if ( UX(end - pc < 5) ) break;         /* don't overrun buffer */
            if ( *pc == '\\' )
            {
               PREPROC_CHANGE( 'X' ); ++pc;        /* #2 backslash */
               PREPROC_CHANGE( 'X' ); ++pc;        /* #3 whatever char follows */
            }
            c = *pc; PREPROC_CHANGE( 'X' ); ++pc;  /* #4 */
            if ( c == '\'' ) break;
            c = *pc; PREPROC_CHANGE( 'X' ); ++pc;  /* #5 */
            if ( c == '\'' ) break;
         }
         break;
      }
   }
   return;
}

/*****************************************************************************
 * Parse every function definition (or class inline method).
 *****************************************************************************/
static void
ParseEvery( char* ibuf, int ilen, parseEveryCallback_t callback )
{
  /*
   * Parse a function definition or inline, move pointer past it, iterate.
   */
   char* pc;
   struct lineCount_t lc;
   ResetLineCount( &lc, ibuf, ilen );
   for ( pc = ibuf; pc < ibuf+ilen; /*empty*/ )
   {
      int   matchLen;
      int   remainder = ilen - (pc - ibuf);
      char* pMatch    = callback( &matchLen, pc, remainder, &lc );
      if ( UX(pMatch == NULL) ) break;
      pc = pMatch + matchLen; /* move past match */
   }
   return;
}

/*****************************************************************************
 * Parse a function definition.
 * @return  If parsed, returns its pointer and non-zero length.
 *          Otherwise returns NULL.
 * @remarks One-liner functions aren't parsed because their braces aren't
 * on the beginning of a line which is what the function searches for
 * (this restriction helps to avoid incorrectly parsing something else).
 *****************************************************************************/
static char*
ParseFuncDef( int* pMatchLen/*OUT*/, char* ibuf, int ilen, struct lineCount_t* lc )
{
   char      saved;
   char*     pFuncDef;
   int       funcDefLen;
   char*     pFuncId;
   int       funcIdLen;
   char*     pBraceLeft;
   char*     pBraceRight;
   int       lineFirst;
   int       lineFinal;
   int       limit;
   char*     pEnd = ibuf + ilen;

  /*
   * Parse a function definition.
   * lineFinal will be computed after function calls are parsed 
   * or otherwise UpdateLineCount()'s recovery case will be hit.
   */
   for ( limit = MAX_FUNC_DEFS_IN_MODULE; limit > 0; --limit )
   {
      pFuncDef = ParseFuncDef2( &funcDefLen, &pFuncId, &funcIdLen, &pBraceLeft, &pBraceRight, ibuf, ilen );
      if ( EX(pFuncDef != NULL) )  break;

     /*
      * If a mismatch occurred (pBraceRight does point to a brace),
      * then continue after the mismatched segment.
      */
      if ( pBraceRight == NULL ) return NULL;
      ilen -= pBraceRight - ibuf;
      ibuf = pBraceRight;
      if ( ibuf >= pEnd ) return NULL; /* past the real end of the buffer? */

      ASSERT_RETURN_VAL_IF_FAIL( limit > 0, NULL );
   }
   if ( UX(pFuncDef == NULL) ) return NULL;
   lineFirst = UpdateLineCount( lc, pFuncDef );

  /*
   * Done parsing the function definition.
   * Compute pointers to within the body of the function definition
   * and pass the segment as a ASCIIZ string on which to parse function calls.
   */
   ++pBraceLeft;  /* past braces */
   --pBraceRight;
   saved = *pBraceRight; *pBraceRight = '\0';
   ParseFuncCalls( pBraceLeft, pBraceRight - pBraceLeft ); /* can tolerate bad length */
   *pBraceRight = saved;

  /*
   * Add the parsed function definition to a graph.
   */
   lineFinal = UpdateLineCount( lc, pBraceRight );
   ParseAddFuncDefToGraph( pFuncId, funcIdLen, lineFirst, lineFinal );
   *pMatchLen = pBraceRight - pFuncDef;
   return pFuncDef;
}

/*****************************************************************************
 * Subroutine of ParseFuncDef().
 * @return  If successful, the return value is a pointer to the function identifier,
 *          and all the "OUT" args are returned-by-reference.
 *          If absolutely no match, NULL is returned.
 *          If a mismatch occurred, NULL is returned also but ppRightBrace is returned
 *          with a valid pointer so that the caller can move a pointer past the mismatch
 *          (sometimes a class/struct declaration was parsed instead) and continue parsing.
 *****************************************************************************/
static char*
ParseFuncDef2( int*   pFuncDefLen,  /*OUT*/
               char** ppFuncId,     /*OUT*/
               int*   pFuncIdLen,   /*OUT*/
               char** ppLeftBrace,  /*OUT*/
               char** ppRightBrace, /*OUT*/
               char*  ibuf,
               int  ilen )
{
   int   lines;
   int   level;                   /* parenthesis level */
   int   argOnLine;               /* true if any char on a line was found that might indicate an arg */
   char* pc;
   char* pLeftParenthesis;
   char* pBufEnd = ibuf + ilen;

   *ppRightBrace = NULL;          /* part of retval */

#ifdef HYPERSRC_DEBUG
   *pFuncDefLen = *pFuncIdLen = 0;
   *ppFuncId = *ppLeftBrace = NULL;
#endif

  /*
   * Find the opening brace on the beginning of the line.
   */
   for ( pc = ibuf; pc < pBufEnd; ++pc )
   {
      pc = strchr( pc, '{' );
      if ( UX(pc == NULL) ) return NULL;
      if ( *(pc-1) == '\n' ) break; /* found brace, but is it on beginning of line? */
      
      /*
       * a bug here!!!
       * int func() {
           ...
         }
         
       */
   }
   if ( UX(pc >= pBufEnd) ) return NULL;
   *ppLeftBrace = pc;

  /*
   * Find the closing brace on the beginning of the line.
   */
   for ( ; pc < pBufEnd; ++pc )
   {
      pc = strchr( pc, '}' );
      if ( UX(pc == NULL) ) return NULL;
      if ( *(pc-1) == '\n' ) break; /* found brace, but is it on beginning of line? */
      
      /*
       * a bug here !!!
       */
   }
   if ( UX(pc >= pBufEnd) ) return NULL;
   *ppRightBrace = pc;

ASSERT( *ppLeftBrace  &&  *ppRightBrace );

  /*
   * Scan backwards from the opening brace for the opening parenthesis.
   * argOnLine becomes true when a char is found that indicates
   * a function argument is on the current line.  If argOnLine stays false,
   * something other than a function definition is being misparsed.
   */
   level = lines = argOnLine = 0;
   for ( pc = *ppLeftBrace; pc >= ibuf; --pc )
   {
      if ( *pc == '(' )
      {
         ++argOnLine;
         --level;
         if ( level == 0 )
         {
            if ( IfFirstLineOfFuncDef( pc, ibuf ) ) break;
         }
         continue;
      }
      else if ( *pc == ')' )
      {
         ++argOnLine;
         ++level;
         continue;
      }
      else if ( *pc == ',' || (knrStyle && *pc == ';') )
      {
         ++argOnLine;
         continue;
      }
      else if ( *pc == '\n' )
      {
         ++lines;
        /*
         * Gone off-track (something else is being parsed)?
         */
         if ( (!argOnLine && lines > 1)  ||  lines > MAX_LINES_FUNC_ARGS ) return NULL;
         argOnLine = 0;
         continue;
      }
   }
   if ( UX(pc < ibuf) ) return NULL; /* if opening parenthesis not found */
   pLeftParenthesis = pc;

  /*
   * Scan backwards for the function identifier.
   */
   *ppFuncId = ScanBackwardsForFuncId( pFuncIdLen, pLeftParenthesis, ibuf );
   if ( UX(*ppFuncId == NULL) ) return NULL;
   *pFuncDefLen = *ppRightBrace - *ppFuncId;
ASSERT_RETURN_VAL_IF_FAIL( *pFuncDefLen > 0  &&  *pFuncIdLen > 0, NULL );
   return *ppFuncId;
}

/*****************************************************************************
 * Parse inline method(s) inside a class declaration.
 * Begins by parsing the entire class declaration, then parses every method inline inside.
 * @return  If parsed, returns its pointer and non-zero length.
 *          Otherwise returns NULL.
 *****************************************************************************/
static char*
ParseClassInlines( int* pMatchLen/*OUT*/, char* ibuf, int ilen, struct lineCount_t* lc )
{
   char*     pc;
   char*     pClassPart1;
   char*     pClassPart2;
   int       classPart1Len;
   int       classPart2Len;
   char*     pPart1;
   int       part1Len;
   int       lineFirst;
   int       lineFinal;
   char*     ibufSaved;         /* doubles as pointer to class declaration */
   int       ilenSaved;         /* doubles as length of  class declaration */
   char*     pAfterClass;
   char      savedAfterClass;
   int       braceLevel;

  /*
   * Parse an entire class declaration.
   */
      /* Parse the first part: class.*\n{ */
   FIND_REGEX( pClassPart1, classPart1Len, ibuf, &re_classPart1 );
   if ( pClassPart1 == NULL ) return NULL;
      /* Parse the last part: the closing brace */
   pClassPart2   = strstr( pClassPart1 + classPart1Len, "\n};" );
   classPart2Len = 3; /* sizeof("\n};" */
   if ( pClassPart2 == NULL ) return NULL;

  /*
   * A class declaration was parsed.  Shrink the buffer to fit it.
   * ibufSaved/ilenSaved doubles as pointer/length of the class declaration.
   */
   ibuf = ibufSaved = pClassPart1;
   ilen = ilenSaved = (pClassPart2 + classPart2Len) - pClassPart1;
   pAfterClass = ibuf+ilen;
   savedAfterClass = *pAfterClass; *pAfterClass = '\0'; /* make ASCIIZ */

Begin:

ASSERT_RETURN_VAL_IF_FAIL( ilen >= 0 && ilen <= ilenSaved, NULL );
   if ( UX(ibuf >= ibufSaved + ilenSaved  ||  ilen < 10) )
      goto Return;

  /*
   * Find start of inline method:
   * Find pattern: method() {
   */
   pPart1 = ParseClassInlines_Part1( &part1Len, ibuf );
   if ( pPart1 == NULL ) goto Return;
   lineFirst = UpdateLineCount( lc, pPart1 );

  /*
   * Find end of inline method:
   * Find its closing brace.
   * Braces may be nested within the inline definition,
   * so the nesting level must be tracked while searching.
   */
   braceLevel = -1; /* count the opening brace which the search skips */
   for ( pc = pPart1+part1Len; *pc; ++pc )
   {
      if ( *pc == '{' )
         --braceLevel;
      else if ( *pc == '}' )
      {
         ++braceLevel;
         if ( braceLevel == 0 )
         {
           /*
            * Done parsing inline method.  Parse function calls inside it.
            * Compute pointers to within the body and pass segment
            * as a ASCIIZ string to ParseFuncCalls().
            */
            char  saved;
            int   idLen;
            char* pBraceLeftPost = pPart1 + part1Len; /* one char after  '{' */
            char* pBraceRightPre = pc - 1;            /* one char before '}' */
            /* The difference of the brace pointers will be 0 or -1 for empty inlines */
            /* such as Foo() { } or Foo(){} but ParseFuncCalls() can tolerate bad lengths. */
            saved = *pc; *pc = '\0'; /* make ASCIIZ */
            ParseFuncCalls( pBraceLeftPost, pBraceRightPre - pBraceLeftPost ); /* can tolerate bad length */
            *pc = saved;

           /*
            * Add inline method to a graph.
            */
            for ( idLen = 0; IsCishIdChar(pPart1[idLen]); ++idLen ) { }
            if ( UX(idLen <= 0 || idLen > MAX_ID_LEN) ) goto Return;
            lineFinal = UpdateLineCount( lc, pc );
            /* ParseAddFuncDefToGraph() also applies to inlines. */
            ParseAddFuncDefToGraph( pPart1, idLen, lineFirst, lineFinal );

           /*
            * Move past inline method and reiterate.
            */
            ibuf = pc + 1;                             /* past closing brace */
            ilen = ilenSaved - (ibuf - ibufSaved);     /* there's a check at Begin */
            goto Begin;
         }
      }
   }
   /* Closing brace not found. */
Return:
   *pAfterClass = savedAfterClass;  /* undo ASCIIZ */
   *pMatchLen = ilenSaved;          /* length of class declaration */
   return ibufSaved;                /* pointer to class declaration */
}

/*****************************************************************************
 * ParseClassInlineMethods() subroutines.
 * These subroutines abstract whether regex functions are used or not.
 *****************************************************************************/
/*
 * Find start of a class inline method:
 * Find pattern: method() {
 */
static char*
ParseClassInlines_Part1( int* pMatchLen/*OUT*/, char* ibuf )
{
   char* p;
   FIND_REGEX( p, *pMatchLen, ibuf, &re_inlinePart1 );
   return p;
}

/*****************************************************************************
 * Iteratively parse function calls.
 * The passed pointer/length (ibuf/ilen) should enclose a function block.
 * ilen <= 0 is acceptable (in which case nothing is done).
 * Function calls are parsed linearly without recursion.
 * @verbatim
 * Eg:
 *    int x = a( b( c() ) );
 * "a(", "b(", "c(" is matched in succession by just scanning linearly.
 * @endverbatim
 *****************************************************************************/
static void
ParseFuncCalls( char* ibuf, int ilen )
{
   char* pc;
   char* pLeftParenthesis;
   char* pFuncId;
   int   funcIdLen;
   char  savedPastId;

  /*
   * SUBTLE: Silently do nothing if ilen <= 0 which can happen on empty inlines
   * such as virtual ~Foo() { } or ~Foo() {} from difference of moving two pointers
   * past the braces.  The condition "pc < ibuf+ilen" happens to handle such cases.
   *
   * Iteratively (linearly) parse function calls.
   */
   for ( pc = ibuf; pc < ibuf+ilen; pc = pLeftParenthesis+1 /* past match */ )
   {
     /*
      * Find the left parenthesis in "func(".
      * Then parse the function identifier.
      */
      pLeftParenthesis = strchr( pc, '(' );
      if ( pLeftParenthesis == NULL ) break;
      pFuncId = ScanBackwardsForFuncId( &funcIdLen, pLeftParenthesis, ibuf );
      if ( UX(pFuncId == NULL) ) continue; /* misparsed something else, continue after it */

     /*
      * Add the parsed function call to a graph.
      */
      savedPastId = pFuncId[funcIdLen]; pFuncId[funcIdLen] = '\0'; /* make ASCIIZ */
      if ( ! IgnoreFuncName( pFuncId, funcIdLen ) )
         AddFuncCallToGraph( pFuncId, funcIdLen );
      pFuncId[funcIdLen] = savedPastId;
   }
   return;
}

/*****************************************************************************
 * Add function definition to graph.
 *****************************************************************************/
static void
ParseAddFuncDefToGraph( char* pFuncId, int funcIdLen, int lineFirst, int lineFinal )
{
   char saved;

ASSERT( pFuncId  &&  funcIdLen > 0 );
ASSERT( lineFirst <= lineFinal );

   saved = pFuncId[funcIdLen]; pFuncId[funcIdLen] = '\0'; /* turn into ASCIIZ string */
   AddFuncDefToGraph( pFuncId, lineFirst+1, lineFinal+1 );
   pFuncId[funcIdLen] = saved;
   return;
}

/*****************************************************************************
 * The lines preceding the beginning of the function body should contain
 * function arguments or an initializer list.  Special cases are parentheses
 * of a K&R style arg or a C++ initialize list whose left parenthensis
 * must not be mistaken from the opening parenthesis.
 * @verbatim
 * void foo( void )
 * {
 * }
 *
 * void foo( int a,
 *           int b )
 * {
 * }
 *
 * void foo( a, funcptr )
 *           int a;
 *           void (*funcptr)(void);
 * {
 * }
 *
 * void Foo::Foo( int a,
 *                int b )
 *     : Bar(a)
 * {
 * }
 * @endverbatim
 *****************************************************************************/
static gboolean
IfFirstLineOfFuncDef( char* pLeftParenthesis, char* ibuf )
{
   char* pc;

  /*
   * Scan forward on this line for a semicolon.
   */
   if ( UX(knrStyle) )
   {
      for ( pc = pLeftParenthesis+1; *pc != '\n' && *pc; ++pc )
      {
         if ( *pc == ';' ) return FALSE;
      }
   }

  /*
   * Scan backwards on this line for a colon (but not "::").
   * -- Even if not browsing C++, execute this loop for the next check. --
   */
   for ( pc = pLeftParenthesis-1; *pc != '\n' && pc >= ibuf; --pc )
   {
      if ( UX(*pc == ':') ) return *(pc-1) == ':';
   }

  /*
   * pc is probably pointing to '\n' (or ibuf-1 which is inaccurate but OK).
   * An identifier char should be the first char on the line.
   * This check also helps to parse C++ constructurs such as:
   * Foo::Foo( int a, int b )
   *     : Base1(a),
   *       Base2(b)
   * {
   * }
   */
   return IsCishIdChar( *(pc+1) ); /* inaccurate but tolerable if pc==ibuf-1 */
}

/*****************************************************************************
 * Scan backwards for the function identifier.
 * Starts from pLeftParenthesis and scans backwards towards the beginning
 * of the buffer (ibuf).
 * @return  Pointer/length of the function identifier or NULL.
 *          If NULL is returned, something else was parsed (eg a parenthesized expression).
 *****************************************************************************/
static char*
ScanBackwardsForFuncId( int* pFuncIdLen/*OUT*/, char* pLeftParenthesis, char* ibuf )
{
   char* pc;
   char* pFuncIdLastChar;

  /*
   * Accept:
   *    foo();
   *    foo ();
   * Reject:
   *    x = (y);
   *    unsigned long pv=(unsigned long)p;
   */
   if (      IsCishIdChar( *(pLeftParenthesis-1) ) )
      pFuncIdLastChar      = pLeftParenthesis-1;
   else if ( IsCishIdChar( *(pLeftParenthesis-2) )  &&  *(pLeftParenthesis-1) == ' ' )
      pFuncIdLastChar      = pLeftParenthesis-2;
   else
      return NULL;

  /*
   * Scan backwards for the first char of the function identifier
   * (actually the char that precedes it).
   */
   for ( pc = pFuncIdLastChar; pc >= ibuf; --pc )
   {
      if ( ! IsCishIdChar(*pc) ) break;
   }
   *pFuncIdLen = pFuncIdLastChar - (pc + 1) + 1;
ASSERT_RETURN_VAL_IF_FAIL( *pFuncIdLen > 0, NULL );
ASSERT_RETURN_VAL_IF_FAIL( *pFuncIdLen < MAX_ID_LEN, NULL );
   return pc + 1;
}

/*****************************************************************************
 * @fn static void ResetLineCount( struct lineCount_t* lc, char* buf, int len );
 * Line count functions.
 * Their purpose is to quicken line counting by remembering the current position,
 * and to maintain different states during parsing.
 * @pre ResetLineCount() must be called first to initialize the lineCount_t struct.
 *****************************************************************************/
#ifdef HYPERSRC_DEBUG
static int lineCountDebug;
#endif
static void
ResetLineCount( struct lineCount_t* lc, char* buf, int len )
{
   lc->cnt           = 0;   /* line #1 is 0 for consistency with ToLine() */
   lc->buf = lc->cur = buf;
   lc->len           = len;
}
#if 0
static void
SetLineCount( struct lineCount_t* lc, int cnt, char* cur )
{
   lc->cnt = cnt;
   lc->cur = cur;
}
#endif
/** This is very frequent. */
static int
UpdateLineCount( struct lineCount_t* lc, char* cur )
{
  /*
   * Picks up where it left off.
   */
ASSERT( (cur >= lc->buf)  &&  (cur < lc->buf + lc->len) );

  /*
   * Recover from unexpected case by forcing recount.
   */
#if HYPERSRC_DEBUG
   if ( UX(cur < lc->cur) ) { lc->cur = lc->buf; lc->cnt = 0; DebugMsg( "UpdateLineCount recovering" ); }
#endif

  /*
   * Count EOL chars from previous counted position
   * til the new current position.
   */
   for ( ; lc->cur <= cur; ++lc->cur ) lc->cnt += (*lc->cur=='\n');
#ifdef HYPERSRC_DEBUG
   lineCountDebug = lc->cnt;
#endif
   return lc->cnt;
}
#ifdef HYPERSRC_DEBUG
static int
DebugGetLineCount( void )
{
   return lineCountDebug;
}
#endif
